/*
 * $Id: sysapi.c 50 2009-02-10 05:48:05Z hyunghwan.chung $
 *
   Copyright 2005-2009 Chung, Hyung-Hwan.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an  BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

#include <xp/bas/sysapi.h>
#include <xp/bas/stdarg.h>
#include <xp/bas/locale.h>
#include <xp/bas/string.h>
#include <xp/bas/mstring.h>
#include <xp/bas/assert.h>

#ifdef _WIN32
	#include <io.h> /* TODO: remove dependency on this */
#else
	#include <sys/stat.h>
	#include <fcntl.h>
#endif

#if defined(XP_USE_SYSCALL) && defined(XP_HAVE_SYS_SYSCALL_H)
	#include <sys/syscall.h>
#endif

xp_mode_t xp_umask (xp_mode_t mode)
{
#ifdef SYS_umask
	return syscall (SYS_umask, mode);
#else
	return umask (mode);
#endif
}

xp_hnd_t xp_open (const xp_char_t* path, int flags, ...)
{
	xp_hnd_t handle;

#ifdef _WIN32

	DWORD desired_access = 0;
	DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
	DWORD creation_disposition = 0;
	DWORD attributes = FILE_ATTRIBUTE_NORMAL;
	DWORD file_type;
	
	/* TODO: xp_seterrno... */

	if (flags & XP_OPEN_APPEND) 
	{
		/* this is not officialy documented for CreateFile.
		* ZwCreateFile (kernel) seems to document it */
		desired_access |= FILE_APPEND_DATA;
	}
	else if (flags & XP_OPEN_WRITE) 
	{
		/* In WIN32, FILE_APPEND_DATA and GENERIC_WRITE can't
		 * be used together */
		desired_access |= GENERIC_WRITE;
	}
	if (flags & XP_OPEN_READ) desired_access |= GENERIC_READ;

	if (flags & XP_OPEN_CREATE) 
	{
		creation_disposition = 
			(flags & XP_OPEN_EXCLUSIVE)? CREATE_NEW:
			(flags & XP_OPEN_TRUNCATE)? CREATE_ALWAYS: OPEN_ALWAYS;
	}
	else if (flags & XP_OPEN_TRUNCATE) 
	{
		creation_disposition = TRUNCATE_EXISTING;
	}
	else creation_disposition = OPEN_EXISTING;

	if (flags & XP_OPEN_NOSHRD) share_mode &= ~FILE_SHARE_READ;
	if (flags & XP_OPEN_NOSHWR) share_mode &= ~FILE_SHARE_WRITE;

	if (flags & XP_OPEN_SYNC) attributes |= FILE_FLAG_WRITE_THROUGH;

	/* TODO: implement nonblock...
	if (flags & XP_OPEN_NONBLOCK) desired_access |= O_NONBLOCK;
	*/

	handle = CreateFile (path, 
		desired_access, share_mode, XP_NULL, 
		creation_disposition, attributes, 0);
	if (handle == XP_HND_INVALID) return XP_HND_INVALID;

	file_type = GetFileType(handle);
	if (file_type == FILE_TYPE_UNKNOWN) 
	{
		CloseHandle (handle);
		return XP_HND_INVALID;
	}

	/*
	if (file_type == FILE_TYPE_CHAR) 
	else if (file_type == FILE_TYPE_PIPE)
	*/
	/* TODO: a lot more */

#else
	int desired_access = 0;
	xp_va_list arg;
	xp_mode_t mode;

	#ifdef XP_CHAR_IS_MCHAR
	const xp_mchar_t* path_mb;
	#else
	xp_mchar_t path_mb[XP_PATH_MAX + 1];
	#endif

	xp_va_start (arg, flags);
	if (XP_SIZEOF(xp_mode_t) < XP_SIZEOF(int))
		mode = xp_va_arg (arg, int);
	else
		mode = xp_va_arg (arg, xp_mode_t);
	xp_va_end (arg);

	#ifdef XP_CHAR_IS_MCHAR
	path_mb = path;
	#else
	if (xp_wcstomcs_strict (
		path, path_mb, xp_countof(path_mb)) == -1) return -1;
	#endif

	/*
	 * rwa -> RDWR   | APPEND
	 * ra  -> RDWR   | APPEND
	 * wa  -> WRONLY | APPEND
	 * a   -> WRONLY | APPEND
	 */
	if (flags & XP_OPEN_APPEND)
	{
		if ((flags & XP_OPEN_READ)) desired_access |= O_RDWR;
		else desired_access |= O_WRONLY;
		desired_access |= O_APPEND;
	}
	else
	{
		if ((flags & XP_OPEN_READ) &&
		    (flags & XP_OPEN_WRITE)) desired_access |= O_RDWR;
		else if (flags & XP_OPEN_READ) desired_access |= O_RDONLY;
		else if (flags & XP_OPEN_WRITE) desired_access |= O_WRONLY;
	}

	if (flags & XP_OPEN_CREATE) desired_access |= O_CREAT;
	if (flags & XP_OPEN_TRUNCATE) desired_access |= O_TRUNC;
	if (flags & XP_OPEN_EXCLUSIVE) desired_access |= O_EXCL;
	if (flags & XP_OPEN_SYNC) desired_access |= O_SYNC;
	if (flags & XP_OPEN_NONBLOCK) desired_access |= O_NONBLOCK;

	#if defined(O_LARGEFILE)
	desired_access |= O_LARGEFILE;
	#endif
		
	#ifdef SYS_open
	handle = syscall (SYS_open, path_mb, desired_access, mode);
	#else
	handle = open (path_mb, desired_access, mode);
	#endif

#endif

	return handle;
}

int xp_close (xp_hnd_t handle)
{
#ifdef _WIN32
	if (CloseHandle(handle) == FALSE) return -1;
	return 0;
#else
	#ifdef SYS_close
	return syscall (SYS_close, handle);
	#else
	return close (handle);
	#endif
#endif
}

xp_ssize_t xp_read (xp_hnd_t handle, void* buf, xp_size_t size)
{
#ifdef _WIN32

	DWORD bytes_read;
	xp_size_t n = 0;
	xp_byte_t* p = (xp_byte_t*)buf;

	while (1) {
		if (size <= XP_TYPE_MAX(DWORD)) {
			if (ReadFile(handle, p, size, 
				&bytes_read, XP_NULL) == FALSE) return -1;
			n += bytes_read;
			break;
		}

		if (ReadFile(handle, p, XP_TYPE_MAX(DWORD),
			&bytes_read, XP_NULL) == FALSE) return -1;
		if (bytes_read == 0) break; /* reached the end of a file */

		p += bytes_read;
		n += bytes_read;
		size -= bytes_read;
	}

	return (xp_ssize_t)n;

#else

	#ifdef SYS_read 
	return syscall (SYS_read, handle, buf, size);
	#else
	return read (handle, buf, size);
	#endif

#endif
}

xp_ssize_t xp_write (xp_hnd_t handle, const void* data, xp_size_t size)
{
#ifdef _WIN32

	DWORD bytes_written;
	xp_size_t n = 0;
	const xp_byte_t* p = (const xp_byte_t*)data;

	while (1) 
	{
		if (size <= XP_TYPE_MAX(DWORD)) {
			if (WriteFile(handle, p, size, 
				&bytes_written, XP_NULL) == FALSE) return -1;
			n += bytes_written;
			break;
		}

		if (WriteFile(handle, p, XP_TYPE_MAX(DWORD), 
			&bytes_written, XP_NULL) == FALSE) return -1;
		if (bytes_written == 0) break;

		p += bytes_written;
		n += bytes_written;
		size -= bytes_written;
	}

	return (xp_ssize_t)n;

#else

	#ifdef SYS_write
	return syscall (SYS_write, handle, data, size);
	#else
	return write (handle, data, size);
	#endif

#endif
}

xp_off_t xp_seek (xp_hnd_t handle, xp_off_t offset, int origin)
{
#ifdef _WIN32

	static int __seek_map[] = 
	{
		FILE_BEGIN,
		FILE_CURRENT,
		FILE_END	
	};
	LARGE_INTEGER x, y;

	XP_ASSERT (XP_SIZEOF(offset) <= XP_SIZEOF(x.QuadPart));

	x.QuadPart = offset;
	if (SetFilePointerEx (
		handle, x, &y, __seek_map[origin]) == FALSE) return -1;

	return (xp_off_t)y.QuadPart;

#else

	static int __seek_map[] = 
	{
		SEEK_SET,
		SEEK_CUR,
		SEEK_END
	};

#if !defined(_LP64) && defined(SYS__llseek)
	loff_t tmp;

	if (syscall (SYS__llseek, handle,
		(unsigned long)(offset>>32),
		(unsigned long)(offset&0xFFFFFFFFlu),
		&tmp,
		__seek_map[origin]) == -1) return -1;

	return (xp_off_t)tmp;

#elif defined(SYS_lseek)
	return syscall (SYS_lseek, handle, offset, __seek_map[origin]);
#elif !defined(_LP64) && defined(HAVE_LSEEK64)
	return lseek64 (handle, offset, __seek_map[origin]);
#else
	return lseek (handle, offset, __seek_map[origin]);
#endif

#endif
}

int xp_fstat (xp_hnd_t handle, xp_stat_t* buf)
{
#ifdef _WIN32
	// TODO:....
	return -1;
#else
	#if !defined(_LP64) && defined(SYS_fstat64)
	return syscall (SYS_fstat64, handle, buf);
	#elif defined(SYS_fstat)
	return syscall (SYS_fstat, handle, buf);
	#elif !defined(_LP64) && defined(HAVE_FSTAT64)
	return fstat64 (handle, buf);
	#else
	return fstat (handle, buf);
	#endif
#endif
}

int xp_fchmod (xp_hnd_t handle, xp_mode_t mode)
{
#ifdef _WIN32
	/* TODO: */
	return -1;
#else
	#ifdef SYS_fchmod
	return syscall (SYS_fchmod, handle, mode);
	#else
	return fchmod (handle, mode);
	#endif
#endif
}

int xp_ftruncate (xp_hnd_t handle, xp_off_t size)
{
#ifdef _WIN32
	LARGE_INTEGER x;
	x.QuadPart = size;

	if (SetFilePointerEx(handle,x,NULL,FILE_BEGIN) == FALSE ||
	    SetEndOfFile(handle) == FALSE) return -1;

	return 0;
#else
 
	#if !defined(_LP64) && defined(SYS_ftruncate64)
	return syscall (SYS_ftruncate64, handle, size);
	#elif defined(SYS_ftruncate)
	return syscall (SYS_ftruncate, handle, size);
	#elif !defined(_LP64) && defined(HAVE_FTRUNCATE64)
	return ftruncate64 (handle, size);
	#else
	return ftruncate (handle, size);
	#endif
#endif
}

